package gragh;

import java.io.Serializable;
import java.util.ArrayList;

import utility.Utility;

import constraint.*;
import constraint.join.Join;
import database.attset.SetAttribute;
import database.constraint.join.ListJoin;
import database.relation.Relation;
import database.relation.Table;

public class Redundancy implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1;
    public int step;
    public ENode node_1;
    public ENode node_2;
    public int fin;
    public boolean havePath;
    public boolean show;
    
    /**
     * 0: binh thuong.
     * 1: tham chieu de quy
     */
    public int special;
    
    public ArrayList<ArrayList<SetAttribute>> paths;
    
    public Redundancy clone() {
        Redundancy r = new Redundancy(this.paths.size());
        
        r.step = this.step;
        r.node_1 = this.node_1;
        r.node_2 = this.node_2;
        r.fin = this.fin;
        r.havePath = this.havePath;
        r.show = this.show;
        for (int i = 0; i < this.paths.size(); i++) {
            for (SetAttribute setAtt : this.paths.get(i)) {
                r.paths.get(i).add((SetAttribute) setAtt.clone());
            }
        }
        r.special = this.special;
        
        return r;
    }

    /**
     * Duong di toi da co the chua 0 -> N dinh, list se co N+1 phan tu
     * @param n
     */
    public Redundancy(int n) {
        show = true;
        fin = 0;
        step = 0;
        havePath = false;
        paths = new ArrayList<ArrayList<SetAttribute>>();
        for (int i = 0; i < n+1; i++) {
            paths.add(new ArrayList<SetAttribute>());
        }
        special = 0;
    }
    
    /**
     * Kiem tra paht co gay ra du hay ko, neu co thi add vao.
     */
    public void updatePaths(SetAttribute tables, SetAttribute paths, ArrayList<Relation> theRelations, ArrayList<Way> theWays, int step) {
        boolean isMatch = false;
        
        isMatch = checkENode(this.node_1, tables, paths, theRelations, theWays);
        if (isMatch) {
            isMatch = checkENode(this.node_2, tables, paths, theRelations, theWays);
        }
        
        if (isMatch) {
            havePath = true;
            this.step = step;
            int n = paths.size();
            this.paths.get(n).add((SetAttribute) paths.clone());
        }
    }
    
    public boolean checkENode(ENode node, SetAttribute tables, SetAttribute paths, ArrayList<Relation> theRelations, ArrayList<Way> theWays) {
        boolean isMatch = true;
        
        if (node.type < 15) {
            isMatch = checkElement(node, tables, paths, theRelations, theWays);
        } else if (node.type < 50) {
            isMatch = checkGroup(node, tables, paths, theWays);
        }
        
        return isMatch;
    }
    
    public boolean checkElement(ENode node, SetAttribute tables, SetAttribute paths, ArrayList<Relation> theRelations, ArrayList<Way> theWays) {
        boolean isMatch = true;
        SetAttribute joinAtt = new SetAttribute();
        for (int i = 1; i < paths.size(); i++) {
            int p = paths.get(i);
            Way w = theWays.get(p);
            for (Integer joinA : w.atts2) {
                if (!joinAtt.contains(joinA)) {
                    joinAtt.add(joinA);
                }
            }
        }
        
        int i = 0;
        while (isMatch && i < node.size()) {
            int att = node.get(i);
            if (joinAtt.contains(att)) {
                isMatch = false;
            } else {
                boolean found = false;
                int k = 0;
                while (!found && k < tables.size()) {
                    Relation r = theRelations.get(tables.get(k));
                    if (att >= r.get(0) && att <= r.get(r.size()-1)) {
                        found = true;
                    }
                    k++;
                }
                isMatch = found;
            }

            i++;
        }
        
        return isMatch;
    }
    
    public boolean checkGroup(ENode node, SetAttribute tables, SetAttribute paths, ArrayList<Way> theWays) {
        boolean isMatch = false;
        
        if (node.isSubSet(tables) && node.path.isSubSet(paths)) {
            if (node.groupLeader.size() > 1) {
                isMatch = true;
            } else {
                int k = 1;
                isMatch = true;
                while (isMatch && k < paths.size()) {
                    Way w = theWays.get(paths.get(k));
                    if (!node.contains(w.table1) && node.contains(w.table2)) {
                        isMatch = false;
                    }
                    k++;
                }
            }
        }
        
        return isMatch;
    }
    
    
    
    /**
     * Ham nay tra tra ve list Redundancy, list nay ko co chua du thua phu thuoc ham va du thua do tham chieu de qui.
     * @param lsE
     * @param lsTable
     * @param lsWay
     */
    public static ListRedundancy RedundancyAnalysis(ArrayList<Equalivation> lsE, ArrayList<Table> lsTable, ArrayList<Way> theWays) {
          // update the groupLeader for Group (ENode with type 1)
        ListJoin lsJoin = null;
        int main1 = 0;
        int main2 = 0;
        SetAttribute atts1 = null;
        SetAttribute atts2 = null;
        SetAttribute groupLeader = new SetAttribute();
        SetAttribute groupTailer = new SetAttribute();
        SetAttribute result = null;
        
        // cap nhat groupLeader phu vu cho viec so sanh ham nhom
        // cap nhat gia tri groupLeader
        for (Equalivation e : lsE) {
            for (ENode n : e.lsNode) {
                if (n.type == 20) {
                    groupLeader = new SetAttribute();
                    groupTailer = new SetAttribute();
                    lsJoin = new ListJoin();
                    atts1 = new SetAttribute();
                    atts2 = new SetAttribute();
                      // cha biet nen lam con khi gi
                      // bc chi co the la co type = { 3 , 5 , 6 }
                    constraint.Constraint bc = n.constraint;
                    ArrayList<Join> join = new ArrayList<Join>();
                    
                    switch (bc.type) {
                    case 3:
                        Constraint_v3 c3 = (Constraint_v3) bc;
                        for (Join jo : c3.leftGroup.join) {
                            join.add(jo.clone());
                        }
                        break;
                    case 5:
                        Constraint_v5 c5 = (Constraint_v5) bc;
                        for (Join jo : c5.group.join) {
                            join.add(jo.clone());
                        }
                        break;
                    case 6:
                        Constraint_v6 c6 = (Constraint_v6) bc;
                        if (n.isLeft) {
                            for (Join jo : c6.leftGroup.join) {
                                join.add(jo.clone());
                            }
                        } else {
                            for (Join jo : c6.rightGroup.join) {
                                join.add(jo.clone());
                            }
                        }
                        break;
                    }
                    // TODO viet lai clone
                    /**
                     *  Neu join.size()=0 thi da dc xu ly luc parse
                     */
                    while (join.size() > 0) {
                        lsJoin.add(join.get(0));
                        join.remove(0);
                        
                        atts1 = new SetAttribute();
                        atts2 = new SetAttribute();
                        atts1.add(lsJoin.get(0).indexAtt1);
                        atts2.add(lsJoin.get(0).indexAtt2);
                                                    
                        main1 = lsJoin.get(0).indexTable1;
                        main2 = lsJoin.get(0).indexTable2;
                        
                          // tim ra nhung cai join la cung 1 dk
                        int i = 0;
                        while (i < join.size()) {
                            int syn1 = join.get(i).indexTable1;
                            int syn2 = join.get(i).indexTable2;
                            if (main1 == syn1 && main2 == syn2) {
                                atts1.add(join.get(i).indexAtt1);
                                atts2.add(join.get(i).indexAtt2);
                                join.remove(i);
                            } else if (main1 == syn2 && main2 == syn1) {
                                atts1.add(join.get(i).indexAtt2);
                                atts2.add(join.get(i).indexAtt1);
                                join.remove(i);
                            } else {
                                i++;
                            }
                        }
                        
                          // quang index cua theWays vao
                          // ktra xem dk nay co table nao leader ko?
                        boolean wFound = false;
                        int wk = 0;
                        while (!wFound && wk < theWays.size()) {
                            Way w = theWays.get(wk);
                            SetAttribute att1 = null;
                            SetAttribute att2 = null;
                            if (w.table1 == main1 && w.table2 == main2) {
                                att1 = Utility.convertArrayListIntToSetAtt(w.atts1);
                                att2 = Utility.convertArrayListIntToSetAtt(w.atts2);
                            } else if (w.table1 == main2 && w.table2 == main1) {
                                att1 = Utility.convertArrayListIntToSetAtt(w.atts2);
                                att2 = Utility.convertArrayListIntToSetAtt(w.atts1);
                            }
                              // = null tuc la cac table ko giong nhau
                            if (att1 != null && att2 != null) {
                                if (atts1.isEqual(att1) && atts2.isEqual(att2)) {
                                    if (!groupLeader.contains(w.table1)) {
                                        groupLeader.add(main1);    
                                    }
                                    if (!groupTailer.contains(w.table2)) {
                                        groupTailer.add(main2);
                                    }
                                    n.path.add(wk);
                                    wFound = true;
                                }
                            }
                            wk++;
                        }

                    }
                    result = groupLeader.difference(groupLeader);
                    n.groupLeader = result;
                }
            }
        }
        
        // chinh thuc tao ra list Redundancy
        ListRedundancy resultR = new ListRedundancy();
        for (Equalivation e : lsE) {
            for (int i = 0; i < e.lsNode.size()-1; i++) {
                for (int j = i+1; j < e.lsNode.size(); j++) {
                    ENode n1 = e.lsNode.get(i);
                    ENode n2 = e.lsNode.get(j);
                    int tType = n1.type + n2.type;
                    boolean canAdd = false;
                    /**
                     * tType < 25       : list Element so voi list Element.
                     * 25 < tType < 35  : list Element so voi group. 
                     * 35 < tType < 90  : group so voi gruop.
                     * 90 < tType < 105 : list Element so voi hang so.
                     * tType > 105      : group so voi hang so hoac hang so so voi hang so.
                     */
                    if (tType < 25) {
                        if (n1.size() == 1 && n1.func.get(0).equals("")) {
                            canAdd = true;    
                        } else if (n2.size() == 1 && n2.func.get(0).equals("")) {
                            canAdd = true;
                        }
                    } else if (tType > 25 && tType < 35) {
                        if (n2.type == 20 && n1.size() == 1 && n1.func.get(0).equals("")) {
                            canAdd = true;
                        } else if (n1.type == 20 && n2.size() == 1 && n2.func.get(0).equals("")) {
                            canAdd = true;
                        }
                    } else if (tType > 35 && tType < 90) {
                        // do nothing
                    } else if (tType > 90 && tType < 105) {
                        if (n2.type == 90 && n1.size() == 1 && n1.func.get(0).equals("")) {
                            canAdd = true;
                        } else if (n1.type == 90 && n2.size() == 1 && n2.func.get(0).equals("")) {
                            canAdd = true;
                        }
                    } else {
                        // do nothing, too
                    }
                    
                    if (canAdd) {
                        Redundancy r = new Redundancy(lsTable.size());
                        r.special = n1.special;
                        r.node_1 = n1;
                        r.node_2 = n2;
                        resultR.add(r);
                    }
                }
            }
        }
        return resultR;
    }
    
    static boolean isOptimizedPaths = false;
    public void optimizePaths() {
        if (!isOptimizedPaths) {
            for (int i1 = paths.size()-2; i1 >= 0; i1--) {
                for (int i2 = 0; i2 < paths.get(i1).size(); i2++) {
                    for (int j1 = i1+1; j1 < paths.size(); j1++) {
                        int j2 = 0;
                        while (j2 < paths.get(j1).size()) {
                            SetAttribute tmp1 = (SetAttribute) paths.get(i1).get(i2).clone();
                            SetAttribute tmp2 = (SetAttribute) paths.get(j1).get(j2).clone();
                            tmp1.remove(0);
                            tmp2.remove(0);
                            if (tmp1.isSubSet(tmp2)) {
                                paths.get(j1).remove(j2);
                            } else {
                                j2++;
                            }
                        }
                    }
                }
            }
        }
    }
    
}
